﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Zen4SyncReport.DAL;
using Microsoft.Office.Interop.Excel;

using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;


namespace Zen4SyncReport.Report
{
    /// <summary>
    /// This class allows the generation of a report for a given Test Session on a given
    /// Excel Worksheet.
    /// </summary>
    public class ReportGenerator
    {
        #region - Nested Types -

        /// <summary>
        /// This class contains all the "static" texts used in the report.
        /// </summary>
        private static class Texts
        {
            public const string ReportTitle = "Zen4Sync Report";

            public const string TestSessionTitle = "Test Session";
            public const string TestSessionName = "Name";
            public const string TestSessionCategory = "Category";
            public const string TestSessionCreatedOn = "Created On (local time)";
            public const string TestSessionState = "State";

            public const string TestServerTitle = "Test Servers";
            public const string TestServerInvolved = "involved";

            public const string TestScenarioTitle = "Test Scenarios";
            public const string TestScenarioActivity = "Activity";
            public const string TestScenarioAverageExecutionTime = "Average Execution Time (ms)";
            public const string TestScenarioSlowestClient = "Slowest Client Time (ms)";
            public const string TestScenarioFastestClient = "Fastest Client Time (ms)";
            public const string TestScenarioClientDelta = "Delta (ms)";
            public const string TestScenarioNoTimingStatistics = "The Test Session is not completed, impossible to generate timing statistics.";

            public const string ErrorsTitle = "Errors";
            public const string ErrorsNoStatistics = "The Test Session is not completed, impossible to generate errors report.";
            public const string ErrorsClientId = "Client ID";
            public const string ErrorsClientName = "Client Name";
            public const string ErrorsActivityId = "Activity ID";
            public const string ErrorsActivityType = "Activity Type";
            public const string ErrorsActivityError = "Error";
            public const string ErrorsNoError = "No errors";
        }

        #endregion - Nested Types -

        #region - Constants -

        /// <summary>
        /// Name of the sheet displaying Test Session details.
        /// </summary>
        private const string TestSessionSheetName = "Test Session";

        /// <summary>
        /// Name of the sheet displaying Test Scenario details.
        /// </summary>
        private const string TestScenarioSheetName = "Test Scenario";
        
        /// <summary>
        /// Name of the sheet displaying errors.
        /// </summary>
        private const string ErrorsSheetName = "Errors";

        /// <summary>
        /// Index of the sheet displaying Test Session details.
        /// </summary>
        private const int TestSessionSheetIndex = 1;

        /// <summary>
        /// Index of the sheet displaying Test Scenario details.
        /// </summary>
        private const int TestScenarioSheetIndex = 2;

        /// <summary>
        /// Index of the sheet displaying errors.
        /// </summary>
        private const int ErrorsSheetIndex = 3;

        #endregion - Constants -

        #region - Private Fields -

        /// <summary>
        /// Data access.
        /// </summary>
        private Zen4SyncDAL _DAL;

        /// <summary>
        /// Stylizer for Excel.
        /// </summary>
        private ExcelStylizer _Stylizer;

        #endregion - Private Fields -

        #region - Constructors and Initialization -

        /// <summary>
        /// Initializes the ReportGenerator.
        /// The given Zen4SyncDAL will be used by the ReportGenerator for all the report generation operations.
        /// </summary>
        /// <param name="dal">DAL to use when generating reports.</param>
        public ReportGenerator(Zen4SyncDAL dal)
        {
            _DAL = dal;
            this._Stylizer = new ExcelStylizer();
        }

        #endregion - Constructors and Initialization -

        #region - Methods -

        /// <summary>
        /// Generates a report on the given Workbook for the given Test Session.
        /// </summary>
        /// <param name="testSession">Target Test Session of the report.</param>
        /// <param name="workbook">Workbook on which the report will be generated.</param>
        public void GenerateReport(testSession testSession, Workbook workbook)
        {
            if (workbook == null)
                workbook = Globals.Zen4SyncAddIn.Application.Workbooks.Add();

            CreateWorksheets(workbook);

            GenerateTestSession(testSession, workbook.Sheets[TestSessionSheetIndex]);
            GenerateTestScenarios(testSession, workbook.Sheets[TestScenarioSheetIndex]);
            GenerateErrors(testSession, workbook.Sheets[ErrorsSheetIndex]);
        }

        /// <summary>
        /// Creates all the Worksheets needed by the report.
        /// All the Worksheets of the given Workbook are deleted before adding new ones.
        /// Existing Worksheets are deleted because it's a way to obtain empty new worksheets.
        /// </summary>
        /// <param name="workbook">The Workbook on which Worksheets will be added.</param>
        private void CreateWorksheets(Workbook workbook)
        {
            int neededWorksheetCount = 3;
            int i;

            // 1. Delete all worksheets but one: a workbook can't be empty.
            for (i = 1; workbook.Sheets.Count > 1; ((Excel.Worksheet)workbook.Sheets[i]).Delete(), i++) ;
            // 2. Add our worksheets
            for (i = 1; workbook.Sheets.Count <= neededWorksheetCount; workbook.Sheets.Add(), i++) ;
            // 3. Delete the last worksheet we did not delete on step 1.
            ((Excel.Worksheet)workbook.Sheets[i]).Delete();

            // Properly name all sheets
            ((Excel.Worksheet)workbook.Sheets[TestSessionSheetIndex]).Name = TestSessionSheetName;
            ((Excel.Worksheet)workbook.Sheets[TestScenarioSheetIndex]).Name = TestScenarioSheetName;
            ((Excel.Worksheet)workbook.Sheets[ErrorsSheetIndex]).Name = ErrorsSheetName;
        }

        /// <summary>
        /// Generates the "Test Session" part of the report, for the given Test Session on the given Worksheet.
        /// </summary>
        /// <param name="testSession">The target Test Session.</param>
        /// <param name="worksheet">The target Worksheet.</param>
        private void GenerateTestSession(testSession testSession, Worksheet worksheet)
        {
            int currentRow = 1;
            /******************* TITLE *******************/
            worksheet.Cells[currentRow, 1].Value = Texts.ReportTitle;
            this._Stylizer.ApplyTitle1Style(worksheet, currentRow, 1, currentRow, 2);
            /*********************************************/
            
            /**************** TEST SESSION ***************/
            currentRow += 2;
            int testSessionStartRow = currentRow;

            worksheet.Cells[currentRow, 1].Value = Texts.TestSessionTitle;
            this._Stylizer.ApplyTitle2Style(worksheet, currentRow, 1, currentRow, 2);

            currentRow++;
            worksheet.Cells[currentRow, 1].Value = Texts.TestSessionName;
            worksheet.Cells[currentRow, 2].Value = testSession.testSession_name;
            currentRow++;
            worksheet.Cells[currentRow, 1].Value = Texts.TestSessionCategory;
            worksheet.Cells[currentRow, 2].Value = testSession.testSessionCategory.testSessionCategory_name;
            currentRow++;
            worksheet.Cells[currentRow, 1].Value = Texts.TestSessionCreatedOn;
            worksheet.Cells[currentRow, 2].Value = testSession.testSession_createdOn.Value.ToLocalTime();
            currentRow++;
            worksheet.Cells[currentRow, 1].Value = Texts.TestSessionState;
            worksheet.Cells[currentRow, 2].Value = testSession.testSessionState.testSessionState_name;

            this._Stylizer.ApplyBackgroundColor(worksheet, testSessionStartRow, 1, currentRow, 2, Properties.Settings.Default.Style_TestSession_BackgroundColor);
            /*********************************************/

            /**************** TEST SERVERS ***************/
            currentRow += 2;
            int testServerSectionStartRow = currentRow;
            
            // Style
            worksheet.Cells[currentRow, 1].Value = Texts.TestServerTitle;
            this._Stylizer.ApplyTitle2Style(worksheet, currentRow, 1, currentRow, 2);

            currentRow++;
            int testServersStartRow = currentRow;
            worksheet.Cells[currentRow, 1].Value = String.Format("{0} {1}", testSession.TestServersInvolvedCount, Texts.TestServerInvolved);
            List<testServer> involvedTestServers = testSession.TestServersInvolved;
            foreach (var server in involvedTestServers)
            {
                worksheet.Cells[currentRow, 2].Value = server.testServer_name;
                currentRow++;
            }
            currentRow--; // Decrement current row because we are down one line too many
            
            // Style
            this._Stylizer.MergeCells(worksheet, testServersStartRow, 1, currentRow, 1);
            this._Stylizer.GetRange(worksheet, testServersStartRow, 1, currentRow, 1).VerticalAlignment = Excel.XlVAlign.xlVAlignCenter;
            this._Stylizer.ApplyBackgroundColor(worksheet, testServerSectionStartRow, 1, currentRow, 2, Properties.Settings.Default.Style_TestServers_BackgroundColor);
            this._Stylizer.AutoFitColumns(worksheet, 1, 1, currentRow, 2);
            /*********************************************/
        }

        /// <summary>
        /// Generates the "Test Scenario" part of the report, for the given Test Session on the given Worksheet.
        /// </summary>
        /// <param name="testSession">The target Test Session.</param>
        /// <param name="worksheet">The target Worksheet.</param>
        private void GenerateTestScenarios(testSession testSession, Worksheet worksheet)
        {
            int currentRow = 1;
            /******************* TITLE *******************/
            worksheet.Cells[currentRow, 1].Value = Texts.TestScenarioTitle;
            this._Stylizer.ApplyTitle1Style(worksheet, currentRow, 1, currentRow, 5);
            /*********************************************/

            currentRow += 2;

            /*************** TEST SCENARIOS **************/
            List<testScenario> involvedScenarios = testSession.TestScenariosInvolved;
            foreach (var scenario in involvedScenarios)
            {
                currentRow = GenerateOneTestScenario(scenario, testSession.testSession_id, testSession.IsCompleted, worksheet, currentRow);
                currentRow += 2;
            }

            this._Stylizer.AutoFitColumns(worksheet, 1, 1, currentRow, 5);
            /*********************************************/
        }

        /// <summary>
        /// Generates a report for the given single Test Scenario in the given Worksheet.
        /// The report consists in a detail of all the activities of the scenario, with timing statistics for each of them.
        /// The end line index is returned.
        /// If the testSession is not completed, timing statistics are not computed.
        /// </summary>
        /// <param name="scenario">Target Test Scenario.</param>
        /// <param name="testSessionId">ID of the Test Session executing the Test Scenario. Needed to generate the statistics of Test Scenario activities.</param>
        /// <param name="worksheet">Target Worksheet.</param>
        /// <param name="startRowIndex">Index of the first line of the report part.</param>
        /// <returns>The index of the last line of the report part.</returns>
        private int GenerateOneTestScenario(testScenario scenario, Guid testSessionId, bool testSessionCompleted, Worksheet worksheet, int startRowIndex)
        {
            int currentRow = startRowIndex;

            /******************* HEADER ******************/
            worksheet.Cells[currentRow, 1].Value = MakeTestScenarioTitle(scenario, testSessionId);
            this._Stylizer.ApplyTitle3Style(worksheet, currentRow, 1, currentRow, 5);
            this._Stylizer.ApplyBackgroundColor(worksheet, currentRow, 1, currentRow, 5, Properties.Settings.Default.Style_TestScenarioTitle_BackgroundColor);

            currentRow++;
            worksheet.Cells[currentRow, 1].Value = Texts.TestScenarioActivity;
            worksheet.Cells[currentRow, 2].Value = Texts.TestScenarioAverageExecutionTime;
            worksheet.Cells[currentRow, 3].Value = Texts.TestScenarioSlowestClient;
            worksheet.Cells[currentRow, 4].Value = Texts.TestScenarioFastestClient;
            worksheet.Cells[currentRow, 5].Value = Texts.TestScenarioClientDelta;
            this._Stylizer.ApplyTableHeaderStyle(worksheet, currentRow, 1, currentRow, 5);
            /*********************************************/

            /***************** ACTIVITIES ****************/
            int activitiesStartRow = currentRow + 1;
            foreach (var activity in scenario.activity)
            {
                currentRow++;
                worksheet.Cells[currentRow, 1].Value = activity.HumanReadableType;
                if (!testSessionCompleted)
                {
                    worksheet.Cells[currentRow, 2].Value = Texts.TestScenarioNoTimingStatistics;
                    Excel.Range currentLine = this._Stylizer.GetRange(worksheet, currentRow, 2, currentRow, 5);
                    currentLine.Merge();
                    currentLine.HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter;
                }
                else
                {
                    int[] activityStats = this._DAL.GetActivityStats(activity.activity_id, scenario.testScenario_id, testSessionId);
                    worksheet.Cells[currentRow, 2].Value = activityStats[0];
                    worksheet.Cells[currentRow, 3].Value = activityStats[1];
                    worksheet.Cells[currentRow, 4].Value = activityStats[2];
                    worksheet.Cells[currentRow, 5].Value = activityStats[3];
                }
            }
            this._Stylizer.ApplyDoubleLineColor(
                worksheet,
                activitiesStartRow,
                1,
                currentRow,
                5,
                Properties.Settings.Default.Style_Table_LineColor1,
                Properties.Settings.Default.Style_Table_LineColor2,
                Properties.Settings.Default.Style_Table_LineBorderColor);
            /*********************************************/

            return currentRow;
        }

        /// <summary>
        /// Generates the "Errors" part of the report on the given Worksheet.
        /// </summary>
        /// <param name="testSession">The target Test Session.</param>
        /// <param name="worksheet">The Worksheet on which the report will be generated.</param>
        private void GenerateErrors(testSession testSession, Worksheet worksheet)
        {
            int currentRow = 1;
            /******************* TITLE *******************/
            worksheet.Cells[currentRow, 1].Value = Texts.ErrorsTitle;
            this._Stylizer.ApplyTitle1Style(worksheet, currentRow, 1, currentRow, 5);
            /*********************************************/

            currentRow += 2;

            /*************** TEST SCENARIOS **************/
            if (!testSession.IsCompleted)
            {
                worksheet.Cells[currentRow, 1].Value = Texts.ErrorsNoStatistics;
                this._Stylizer.ApplyTitle3Style(worksheet, currentRow, 1, currentRow, 5);
                this._Stylizer.GetRange(worksheet, currentRow, 1, currentRow, 5).UnMerge(); // We don't want the cells to be merged
                return;
            }

            List<testScenario> involvedScenarios = testSession.TestScenariosInvolved;
            foreach (var scenario in involvedScenarios)
            {
                currentRow = GenerateOneTestScenarioErrors(scenario, testSession, worksheet, currentRow);
                currentRow += 2;
            }
            /*********************************************/

            // Update columns width
            this._Stylizer.AutoFitColumns(worksheet, 1, 1, currentRow, 5);
        }

        /// <summary>
        /// Generates a report part for the errors of the given Test Scenario executed in the given Test Session.
        /// </summary>
        /// <param name="scenario">Target Test Scenario.</param>
        /// <param name="testSession">Test Session owning the Test Scenario.</param>
        /// <param name="worksheet">Target Worksheet.</param>
        /// <param name="startRowIndex">Index of the row where the report section will start.</param>
        /// <returns>Index of the row where the report section ended.</returns>
        private int GenerateOneTestScenarioErrors(testScenario scenario, testSession testSession, Worksheet worksheet, int startRowIndex)
        {
            int currentRow = startRowIndex;

            /******************* HEADER *******************/
            worksheet.Cells[currentRow, 1].Value = MakeTestScenarioTitle(scenario, testSession.testSession_id);
            this._Stylizer.ApplyTitle3Style(worksheet, currentRow, 1, currentRow, 5);
            this._Stylizer.ApplyBackgroundColor(worksheet, currentRow, 1, currentRow, 5, Properties.Settings.Default.Style_TestSessionErrors_BackgroundColor);
            
            currentRow++;
            worksheet.Cells[currentRow, 1].Value = Texts.ErrorsClientId;
            worksheet.Cells[currentRow, 2].Value = Texts.ErrorsClientName;
            worksheet.Cells[currentRow, 3].Value = Texts.ErrorsActivityId;
            worksheet.Cells[currentRow, 4].Value = Texts.ErrorsActivityType;
            worksheet.Cells[currentRow, 5].Value = Texts.ErrorsActivityError;
            this._Stylizer.ApplyTableHeaderStyle(worksheet, currentRow, 1, currentRow, 5);
            /*********************************************/

            /******************* ERRORS ******************/
            List<ClientError> errors = this._DAL.GetClientsErrors(testSession, scenario);
            if (errors.Count == 0)
            {
                currentRow++;
                worksheet.Cells[currentRow, 1].Value = Texts.ErrorsNoError;
                this._Stylizer.MergeCells(worksheet, currentRow, 1, currentRow, 5);
                this._Stylizer.ApplyDoubleLineColor(worksheet, currentRow, 1, currentRow, 5, Properties.Settings.Default.Style_Table_LineColor1, Properties.Settings.Default.Style_Table_LineColor2, Properties.Settings.Default.Style_Table_LineBorderColor);
                this._Stylizer.GetRange(worksheet, currentRow, 1, currentRow, 5).HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter;
                return currentRow;
            }

            int errorStartRow = currentRow + 1;
            foreach (ClientError err in errors)
            {
                currentRow++;
                worksheet.Cells[currentRow, 1].Value = err.ClientId.ToString();
                worksheet.Cells[currentRow, 2].Value = err.ClientName;
                worksheet.Cells[currentRow, 3].Value = err.ActivityId.ToString();
                worksheet.Cells[currentRow, 4].Value = err.ActivityType;
                worksheet.Cells[currentRow, 5].Value = err.Error;
            }
            this._Stylizer.ApplyDoubleLineColor(
                worksheet,
                errorStartRow,
                1,
                currentRow,
                5,
                Properties.Settings.Default.Style_Table_LineColor1,
                Properties.Settings.Default.Style_Table_LineColor2,
                Properties.Settings.Default.Style_Table_LineBorderColor);
            /*********************************************/

            return currentRow;
        }

        /// <summary>
        /// Computes a string briefly describing the given Test Scenario.
        /// </summary>
        /// <param name="scenario">Target Test Scenario.</param>
        /// <param name="testSessionId">ID of the Test Session owning the Test Scenario.</param>
        /// <returns>A string briefly describing the given Test Scenario.</returns>
        private string MakeTestScenarioTitle(testScenario scenario, Guid testSessionId)
        {
            int activitiesCount = scenario.activity.Count;
            int clientsCount = this._DAL.GetClientSessionsInvolvedCount(testSessionId, scenario.testScenario_id);

            return String.Format(
                "{0} - {1} activit{3} - {2} client{4}"
                , scenario.testScenario_name
                , scenario.activity.Count
                , this._DAL.GetClientSessionsInvolvedCount(testSessionId, scenario.testScenario_id)
                , activitiesCount > 1 ? "ies" : "y"
                , clientsCount > 1 ? "s" : "");
        }
        
        #endregion - Methods -
    }
}
